import React from "react"
import ReactDOM from "react-dom"
import { Provider } from 'react-redux'
import createSagaMiddleware from 'redux-saga'
import * as sagaEffects from 'redux-saga/effects'
import { createStore, combineReducers, applyMiddleware } from "redux"
import { composeWithDevTools } from 'redux-devtools-extension'
import { connectRouter, routerMiddleware } from "connected-react-router"
import { createBrowserHistory } from 'history'

/**
 *创建dva对象函数
 * @export
 * @param {*} [opt={}]
 */
export default function dva(opt = {}) {
    let options = getOptions()
    // const { history, initialState, onError, onAction, onStateChange } = options
    const app = {
        _models: [], // 记录已经定义的模型
        _router: null,
        model,
        router,
        start,
        use
    }

    function getOptions() {
        let options = Object.assign({}, {
            history: createBrowserHistory(),
            initialState: {}, // 初始状态
            onError: () => { }, // 一般在副作用的地方发生错误
            onAction: [],
            onStateChange: () => { },
            onReducer: reducer => (state, action) => reducer(state, action),
            onEffect(effect, sagaEffects, model, actionType) {
                return function* (action) {
                    yield effect(action)
                }
            },
            extraReducers: {},
            extraEnhancers: []
        }, opt)

        if (!Array.isArray(options.onAction)) {
            options.onAction = [options.onAction]
        }

        return options
    }

    /**
     * 使用dva插件
     * @param {*} plugin 一个配置对象
     */
    function use(plugin) {
        options = { ...options, ...plugin }
    }

    /**
     * 根据模型对象定义一个模型
     * @param {*} modelObj
     */
    function model(modelObj) {
        app._models.push(modelObj)
    }

    /**
     * 传入路由函数，返回一路由配置
     * @param {*} routerFunc
     */
    function router(routerFunc) {
        app._router = routerFunc
    }

    /** 运行注册函数subscriptions */
    function runSubscriptions(store) {
        app._models.forEach(model => {
            if (model.subscriptions) {
                function dispatch(action) {
                    store.dispatch(getNewAction(action, model))
                }
                Object.values(model.subscriptions).forEach(func => func({ dispatch, history: options.history }))
            }
        })
    }

    /**
     * 将action的类型绑定到模型
     * @param {*} action 
     * @param {*} model 
     */
    function getNewAction(action, model) {
        let newAction = action;
        if (!action.type.includes("/")) { //如果没有加入命名空间，将当前对象的命名空间加上
            newAction = {
                ...action,
                type: `${model.namespace}/${action.type}`
            }
        }
        return newAction;
    }


    /**
     * 启动dva任务
     * @param {*} selector
     */
    function start(selector) {
        const store = getStore()
        runSubscriptions(store)
        render(selector, store)
    }

    /** 得到一个仓库 */
    function getStore() {
        // 根据模型，得到一个根的reducer
        const reducerObj = app._models.reduce((prev, model) => {
            const { name, reducerFun } = getReducer(model)
            prev[name] = reducerFun
            return prev
        }, {})
        let rootReducer = combineReducers({ ...reducerObj, ...getExtraReducers() })
        const oldeducer = rootReducer

        // 处理onStateChange
        rootReducer = function (state, action) {
            const newState = oldeducer(state, action)
            options.onStateChange(newState)
            return newState
        }

        // 处理onReducer
        const oldeducer2 = rootReducer
        rootReducer = function (state, action) {
            return options.onReducer(oldeducer2)(state, action)
        }

        // 处理 extraEnhancers
        const newCreateStore = options.extraEnhancers.reduce((createStore, func) => func(createStore), createStore)

        // 创建仓库 reducer 初始状态 中间件
        const store = newCreateStore(
            rootReducer,
            options.initialState,
            getMiddlewares())

        // 运行saga
        // @ts-ignore
        getMiddlewares.runSaga(store)

        // window.store = store
        return store
    }

    /** 渲染 */
    function render(selector, store) {
        const routerConfig = app._router({ app, history: options.history })
        const root = (<Provider store={store}>
            {routerConfig}
        </Provider>)
        ReactDOM.render(root, document.querySelector(selector))
    }

    /** 获取额外的reducer */
    function getExtraReducers() {
        return {
            router: connectRouter(options.history), // 把路由放到仓库里面
            ['@@/dva'](state = 0, action) {
                return state
            },
            ...options.extraReducers
        }
    }

    function getMiddlewares() {
        const sagaMid = createSagaMiddleware() // Saga 中间件
        // @ts-ignore
        getMiddlewares.runSaga = function (store) {
            const generatorFunc = []
            app._models.forEach((model) => {

                // effects 
                if (model.effects) {
                    function put(action) { // 处理put,与模型进行关联  
                        return sagaEffects.put(getNewAction(action, model))
                    }
                    Object.entries(model.effects).forEach(([key, value]) => {
                        generatorFunc.push({ type: model.namespace + '/' + key, value, put, model })
                    });
                }
            })
            sagaMid.run(function* () {
                for (const item of generatorFunc) {
                    const { value, type, put, model } = item
                    let func = function* (action) {
                        try {
                            yield value(action, { ...sagaEffects, put })
                        } catch (e) {
                            options.onError(e, store.dispatch)
                        }
                    }
                    // 进一步封装 onEffect 
                    let oldEffect = func
                    func = options.onEffect(oldEffect, sagaEffects, model, type)

                    yield sagaEffects.takeEvery(type, func)
                }
            })
        }
        const mids = [routerMiddleware(options.history), sagaMid, ...options.onAction]
        return composeWithDevTools(applyMiddleware(...mids))
    }

    /** 根据一个模型得到一个reducer */
    function getReducer(model) {
        const { namespace, state: _state, reducers = {} } = model
        const actionTypes = Object.entries(reducers).map(([n, reducer]) => ({ type: namespace + '/' + n, reducer }))
        return {
            name: namespace,
            reducerFun(state = _state, action) {
                const { reducer } = actionTypes.find(it => it.type === action.type) || {}
                if (reducer) {
                    return reducer(state, action)
                } else { return state }
            }
        }
    }

    return app
}


